﻿using System;
using System.Linq;
using System.Linq.Expressions;
using Castle.DynamicProxy;
using Framework.Core.Reflection;

namespace Framework.Core.Recorder
{
    internal class DependencyReplayer : IInterceptor
    {
        private readonly IDependencyRepository dependencyRepository;
        private readonly ICallKeyBuilder callKeyBuilder;
        private readonly IMethodFactory methodFactory;

        internal DependencyReplayer(IDependencyRepository dependencyRepository,
                                    ICallKeyBuilder callKeyBuilder,
                                    IMethodFactory methodFactory)
        {
            this.dependencyRepository = dependencyRepository;
            this.callKeyBuilder = callKeyBuilder;
            this.methodFactory = methodFactory;
        }

        public void Intercept(IInvocation invocation)
        {
            MockInvocation(invocation, GetFromRepository(methodFactory.Create(invocation)));
        }

        private DependencyCall GetFromRepository(IMethod method)
        {
            return GetFromRepository(callKeyBuilder.Build(method), method);
        }        
        
        private DependencyCall GetFromRepository(string recordKey, IMethod method)
        {
            bool isRecorded;

            var returnValue = dependencyRepository.TryGet(recordKey, out isRecorded);

            if (!isRecorded)
            {
                throw new NotRecordedException(string.Format("call {{{0}}} is not recorded", method));
            }
            return returnValue;
        }

        private static void MockInvocation(IInvocation invocation, DependencyCall dependencyCall)
        {
            var args = invocation.Arguments;
            var dependencyParameters = dependencyCall.Parameters;

            invocation.ReturnValue = dependencyCall.ReturnValue;

            var dependencyParametersList = dependencyParameters.ToList();

            for (var i = 0; i < args.Count(); i++)
            {
                args[i] = dependencyParametersList[i].Value;
            }
        }

        public void Override<T, R>(Expression<Func<T, R>> func, R value)
        {
            var method = methodFactory.Create(func);
            var recordKey = callKeyBuilder.Build(method);

            dependencyRepository.Update(recordKey, new DependencyCall(value, GetFromRepository(recordKey, method).Parameters));
        }
    }
}